#include <sysdep.h>
#include <number.h>
#include <utils.h>
#include <ctype.h>
#include <string.h>

#define CONF_MAX_LINE_WIDTH	80
#define CONF_MAX_BODY_LINES	10

#define SH_OP_CMP_LARGE		3
#define SH_OP_CMP_EQUAL		2
#define SH_OP_CMP_SMALL		1

#define STACK_COUNT	10

extern int gloop;

struct sh_stack {
	int idx;	/* used index */
	int count;	/* total space */
	/* expression is made up by three types */
	int type;
#define T_OPERAND	1
#define T_OPERATOR	2
#define T_DELIMITER	3
#define T_END		4
#define T_INVALID	5
	union {
		number *operand;
		char *operator;		/* contain delimiter */
	} u;
};

/* operand and operator stack */
struct sh_stack *s_optr = NULL, *s_opnd = NULL;

#define goto_eol(buf, idx)			\
	do {					\
		while (buf[idx++] != '\n')	\
			;			\
	} while (0)

struct conf_block {
	uint8_t parent;
#define CONF_BLOCK_NONE		0xFF
#define CONF_BLOCK_OPERATOR	0x01
	uint8_t type;
/* block operator's sub type */
#define CONF_OP_ADD	0x01
#define CONF_OP_MIN	0x02
#define CONF_OP_MUL	0x03
#define CONF_OP_DIV	0x04
	const char *name;
};

struct conf_block conf_block_table[] = {
	{CONF_BLOCK_NONE, CONF_BLOCK_OPERATOR, "operator"},
	{CONF_BLOCK_NONE, CONF_BLOCK_NONE,      NULL},
};

struct conf_block conf_sub_block_table[] = {
	{CONF_BLOCK_OPERATOR, CONF_OP_ADD, "op_add"},
	{CONF_BLOCK_OPERATOR, CONF_OP_MIN, "op_min"},
	{CONF_BLOCK_OPERATOR, CONF_OP_MUL, "op_mul"},
	{CONF_BLOCK_OPERATOR, CONF_OP_DIV, "op_div"},
	{CONF_BLOCK_NONE,     CONF_BLOCK_NONE,	NULL},
};

static char sh_op_add	= '+';
static char sh_op_min	= '-';
static char sh_op_mul	= '*';
static char sh_op_div	= '/';

static int conf_fd = -1;

/* parse config file */
static void __set_value(uint8_t type, char *word);
static void __get_value(const char *line, char *word);
static void __get_word(const char *line, char *word, char separator);
static uint8_t shell_word_is_sub_block(const char *name);
static int __get_start_line(int fd, char *buff);
static int __get_body_line(int fd, char *buff);

/* expr stack */
static int sh_stack_init(void);
static void sh_stack_exit(void);
static void *sh_stack_top(struct sh_stack *s);
static int sh_stack_count(struct sh_stack *s);
static void sh_stack_push(struct sh_stack *stack, const char *str);
static void *sh_stack_pop(struct sh_stack *stack);

/* parse expression which is a string type */
static int sh_char_type(char ch);
static int sh_parse_exp(const char *str);

/* calculate and may invoke number's API */
static int sh_optr(char op);
static int sh_cmp_optr(char c1, char c2);
static number sh_do_op(number n1, number n2, char op);
static char *sh_sub(char *sa, char *sb);
static char *sh_add(char *sa, char *sb);

static uint8_t shell_word_is_sub_block(const char *name)
{
	int i = 0;
	
	while (conf_sub_block_table[i].type != CONF_BLOCK_NONE) {
		if (strcasecmp(name, conf_sub_block_table[i].name) == 0)
			return conf_sub_block_table[i].type;
		i++;
	}
	return CONF_BLOCK_NONE;
}

static void __get_value(const char *line, char *word)
{
	__get_word(line, word, '\n');
}

static void __set_value(uint8_t type, char *word)
{
	switch (type) {
		/* XXX: some type may need strdup */
	case CONF_OP_ADD:
		sh_op_add = word[0];
		break;
	case CONF_OP_MIN:
		sh_op_min = word[0];
		break;
	case CONF_OP_MUL:
		sh_op_mul = word[0];
		break;
	case CONF_OP_DIV:
		sh_op_div = word[0];
		break;
	default:
		calc_log(LOG_SHELL_ERR, "no type fix");
		break;
	}
}

static void __get_word(const char *line, char *word, char separator)
{
	int i = 0;

	while (line[i++] != separator)
		;
	strncpy(word, line, i - 1);
	word[i-1] = '\0';
	/*
	calc_log(LOG_SHELL_DEBUG, "get word = %s", word);
	*/
}

static int __get_start_line(int fd, char *buff)
{
	char *head, word[CONF_MAX_LINE_WIDTH];
	int len, idx;
rd:
	len = read(fd, buff, CONF_MAX_LINE_WIDTH);
	if (len <= 0) {
		/* EOF or error */
		return len;
	}

	idx = 0;
parse:
	if (buff[idx] == '#') {
		/* go to next line */
		goto_eol(buff, idx);
		goto parse;
	}
	if (idx >= CONF_MAX_LINE_WIDTH)
		goto rd;

	/* body line start with TAB */
	if (buff[idx] == '\t') {
		/* go to next line */
		goto_eol(buff, idx);
		goto rd;
	}
	if (idx >= CONF_MAX_LINE_WIDTH)
		goto rd;

	/* end line start with '}' */
	if (buff[idx] == '}') {
		/* go to next line */
		goto_eol(buff, idx);
		goto rd;
	}
	if (idx >= CONF_MAX_LINE_WIDTH)
		goto rd;

	/* it must start line, lets cut off */
	__get_word(&buff[idx], word, ' ');

	head = &buff[idx];
	goto_eol(buff, idx);

	/* return this line length */
	return &buff[idx] - head;
}

static int __get_body_line(int fd, char *buff)
{
	char *head, word[CONF_MAX_LINE_WIDTH];
	int len, idx, wlen;
	uint8_t type;

	memset(word, 0, CONF_MAX_LINE_WIDTH);

rd:
	len = read(fd, buff, CONF_MAX_LINE_WIDTH);
	if (len <= 0) {
		/* EOF or error */
		return len;
	}

	idx = 0;
parse:
	if (buff[idx] == '#') {
		/* go to next line */
		goto_eol(buff, idx);
		goto parse;
	}
	if (idx >= CONF_MAX_LINE_WIDTH)
		goto rd;

	/* body line start with TAB */
	if (buff[idx] != '\t') {
		/* go to next line */
		goto_eol(buff, idx);
		goto rd;
	}
	if (idx >= CONF_MAX_LINE_WIDTH)
		goto rd;
	/* skip TAB */
	idx++;
	__get_word(&buff[idx], word, ' ');

	wlen = strlen(word);
	
	type = shell_word_is_sub_block(word);

	if (type != CONF_BLOCK_NONE) {
		__get_value(&buff[idx+wlen+3], word);
		__set_value(type, word);
	}

	/* note head contains 'TAB' */
	head = &buff[idx - 1];
	goto_eol(buff, idx);

	/* return this line length */
	return &buff[idx] - head;
}

static int shell_parse_conf(int fd)
{
	int total, len = 0;
	char start[CONF_MAX_LINE_WIDTH];
	char body[CONF_MAX_LINE_WIDTH * CONF_MAX_BODY_LINES];
	char end[CONF_MAX_LINE_WIDTH];
	
	/* go to head */
	total = len = 0;

	memset(start, 0, CONF_MAX_LINE_WIDTH);
	memset(end, 0, CONF_MAX_LINE_WIDTH);
	memset(body, 0, CONF_MAX_BODY_LINES*CONF_MAX_LINE_WIDTH);

	lseek(fd, 0, SEEK_SET);

	/* name {		<-- start line
	 *	node1 = value	<-- body  line
	 *	node2 = value	<-- body  line
	 * }			<-- end   line
	 */
	len = __get_start_line(fd, start);
	if (len <= 0) {
		/* since have no start line, we exit parse */
		return 0;
	}

	lseek(fd, total + len, SEEK_SET);
	total += len;

get_body:
	len = __get_body_line(fd, body);
	if (len <= 0) {
		goto get_end;
	}
	lseek(fd, total + len, SEEK_SET);
	total += len;
	goto get_body;

get_end:
	return 0;
#if 0
	len = __get_body_line(fd, end);
	if (len <= 0) {
		/* since have no end line, we exit parse */
		return 0;
	}
#endif
}

static char *sh_add(char *sa, char *sb)
{
	number result = NULL;
	number n1 = str_to_number(sa);
	number n2 = str_to_number(sb);
	
	result = base_add(n1, n2);
	free_nbr(result);

	return NULL;
}

static char *sh_sub(char *sa, char *sb)
{
	number result = NULL;
	number n1 = str_to_number(sa);
	number n2 = str_to_number(sb);
	
	result = base_sub(n1, n2);
	free_nbr(result);

	return NULL;
}

static number sh_do_op(number n1, number n2, char op)
{
	if (op == '+')
		return base_add(n1, n2);
	if (op == '-')
		return base_sub(n1, n2);
	if (op == '*')
		return base_mul(n1, n2);
	return NULL;
}

static void shell_quit(void)
{
	calc_log(LOG_SHELL_DEBUG, "shell quit main loop");
	gloop = 0;
}

int shell_test(void)
{
	char exp[1024];
	
	memset(exp, 0, 1024);
	while (1) {
		printf("Input expression, [Q/q] for out:\r\n");
	
		memset(exp, 0, 1024);
		scanf("%s", exp);
		
		if (exp[0] == 'q' || exp[0] == 'Q') {
			break;
		}
		sh_parse_exp(exp);
		
	}
	shell_quit();
	return 0;
}

struct sh_stack *sh_stack_alloc(struct sh_stack *stack, int type)
{
	if (!stack) {
		stack = malloc(sizeof (struct sh_stack));
		if (!stack) {
			calc_log(LOG_SHELL_ERR, "out of memory");
			return NULL;
		}
		memset(stack, 0, sizeof (struct sh_stack));
	}

	if (type == T_OPERAND) {
		stack->u.operand = malloc(sizeof (number) * STACK_COUNT);
		if (!stack->u.operand) {
			calc_log(LOG_SHELL_ERR, "out of memory");
			free(stack);
			return NULL;
		}
		memset(stack->u.operand, 0, sizeof (number) * STACK_COUNT);
	} else {
		stack->u.operator = malloc(sizeof (char) * STACK_COUNT);
		if (!stack->u.operand) {
			calc_log(LOG_SHELL_ERR, "out of memory");
			free(stack);
			return NULL;
		}
		memset(stack->u.operand, 0, sizeof (char) * STACK_COUNT);
	}
	stack->idx = 0;
	stack->count = STACK_COUNT;
	stack->type = type;

	return stack;
}

static int sh_stack_init(void)
{
	s_optr = sh_stack_alloc(s_optr, T_OPERATOR);
	if (!s_optr)
		return -1;

	s_opnd = sh_stack_alloc(s_opnd, T_OPERAND);
	if (!s_opnd)
		return -1;
	return 0;
}

static void sh_stack_exit(void)
{
	if (s_optr) {
		if (s_optr->u.operator)
			free(s_optr->u.operator);
		free(s_optr);
		s_optr = NULL;
	}
	if (s_opnd) {
		if (s_opnd->u.operand)
			free(s_opnd->u.operand);
		free(s_opnd);
		s_opnd = NULL;
	}
}

static void *sh_stack_top(struct sh_stack *s)
{
	if (s) {
		if (s->idx <= 0)
			return NULL;

		if (s->type == T_OPERATOR)
			return (void *)(s->u.operator + (s->idx - 1));
		else
			return (void *)(s->u.operand + (s->idx - 1));
	}
	return NULL;
}

static int sh_stack_count(struct sh_stack *s)
{
	if (s)
		return s->idx;
	return 0;
}

static void sh_stack_push(struct sh_stack *stack, const char *str)
{
	if (stack) {
		if (stack->idx >= stack->count) {
			//calc_panic("need realloc");
		}

		if (stack->type == T_OPERATOR) {
			stack->u.operator[stack->idx++] = str[0];
		} else {
			number tmp = str_to_number((char *)str);
			stack->u.operand[stack->idx++] = tmp;
		}
	}
#ifdef DEBUG_STACK
	calc_log(LOG_SHELL_DEBUG, "push [%c] into %s", str[0], 
 		 stack->type == T_OPERATOR ? "optr" : "opnd");
#endif
}

static void *sh_stack_pop(struct sh_stack *stack)
{
	void *v;

	v = sh_stack_top(stack);

	if (v) {
		stack->idx--;
#ifdef DEBUG_STACK
		calc_log(LOG_SHELL_DEBUG, "push [%c] into %s", (char)v, 
 			 stack->type == T_OPERATOR ? "optr" : "opnd");
#endif
	}
	return v;

}

static int sh_char_type(char ch)
{
	switch (ch) {
	case '(': case ')': case '+': case '-': case '*': case '/':
		return T_OPERATOR;
	case '#':
		return T_END;
	default:
		/* FIXME: '.' should not be the first char, need valid check */
		if (isxdigit(ch) || ch == '.')
			return T_OPERAND;
		return T_INVALID;
	}
}

/* ======================================= *
 * from ds book: operator priority table
 *======================================== *
 *| op1 / op2 | + | - | * | / | ( | ) | # |
 *|  +	      | > | > | < | < | < | > | > |
 *|  -	      | > | > | < | < | < | > | > |
 *|  *	      | > | > | > | > | < | > | > |
 *|  /	      | > | > | > | > | < | > | > |
 *|  (	      | < | < | < | < | < | = |   |
 *|  )	      | > | > | > | > |   | > | > |
 *|  #	      | < | < | < | < | < |   | = |
 * ======================================= */

#define SH_OPTR_ADD	0
#define SH_OPTR_SUB	1
#define SH_OPTR_MUL	2
#define SH_OPTR_DIV	3
#define SH_OPTR_LB	4	/* left brace */
#define SH_OPTR_RB	5
#define SH_OPTR_HASH	6
#define SH_OPTR_NUM	7

int sh_oprt_pri_table[SH_OPTR_NUM][SH_OPTR_NUM] = {
	/* + */
	{1, 1, -1, -1, -1, 1, 1},
	/* - */
	{1, 1, -1, -1, -1, 1, 1},
	/* * */
	{1, 1, 1, 1, -1, 1, 1},
	/* / */
	{1, 1, 1, 1, -1, 1, 1},
	/* ( */
	{-1, -1, -1, -1, -1, 0, -99},
	/* ) */
	{1, 1, 1, 1, -99, 1, 1},
	/* # */
	{-1, -1, -1, -1, -1, -99, 0},
};

static int sh_optr(char op)
{
	switch (op) {
	case '+': return SH_OPTR_ADD;
	case '-': return SH_OPTR_SUB;
	case '*': return SH_OPTR_MUL;
	case '/': return SH_OPTR_DIV;
	case '(': return SH_OPTR_LB;
	case ')': return SH_OPTR_RB;
	case '#': return SH_OPTR_HASH;
	default	: return SH_OPTR_NUM;
	}
}

static int sh_cmp_optr(char c1, char c2)
{
	int id1, id2;

	id1 = sh_optr(c1);
	id2 = sh_optr(c2);

	/* invalid optr */
	if (id1 == SH_OPTR_NUM || id2 == SH_OPTR_NUM) {
		calc_log(LOG_SHELL_ERR, "BUG");
		while (1)
			;
	}

	if (sh_oprt_pri_table[id1][id2] > 0)
		return SH_OP_CMP_LARGE;
	else if (sh_oprt_pri_table[id1][id2] == 0)
		return SH_OP_CMP_EQUAL;
	else if (sh_oprt_pri_table[id1][id2] == -1)
		return SH_OP_CMP_SMALL;

	panic("optr err");
	return -1;
}

/* parse expression string, add `#' to the end first. */
static int sh_parse_exp(const char *str)
{
	char *p, buf[1024];
	char *new = strndup(str, (size_t)(strlen(str) + 1));
	int ret, last, this, i = 0;
	number *retval;

	/* end char */
	new[strlen(str)] = '#';

	p = new;

	memset(buf, 0, 1024);

	ret = sh_stack_init();
	if (ret != 0)
		goto err;
	/* start parse */
	sh_stack_push(s_optr, "#");

	/* goto first valid char */
	while (*p == ' ' || *p == '\t')
		p++;

	last = sh_char_type(*p);

	while (((this = sh_char_type(*p)) != T_END) ||
	       sh_char_type(*(char *)sh_stack_top(s_optr)) != T_END) {

		/* TODO: cannot parse 3+(-2) */
		if (this == T_OPERAND || 
		    (last == T_OPERATOR && this == T_OPERATOR)) {
			buf[i++] = *p++;
			/* for cut the whole number */
			last = this;
			continue;
		} else if (last == T_OPERAND &&
			   (this == T_OPERATOR || this == T_END)) {
			char op;
			size_t len;
			number *n1, *n2, res;
			char buff[1024];

			/* get a whole number */
			buf[i] = '\0';
			sh_stack_push(s_opnd, buf);
			/* reset */
			i = 0;
			last = this;

			switch (sh_cmp_optr(*(char *)sh_stack_top(s_optr), *p)) {
			case SH_OP_CMP_SMALL:
				sh_stack_push(s_optr, p);
				if (this != T_END)
					p++;
				continue;
			case SH_OP_CMP_EQUAL:
				sh_stack_pop(s_optr);
				if (this != T_END)
					p++;
				continue;
			case SH_OP_CMP_LARGE:
				if (this != T_END)
					p++;
				/* cannot do it */
				if (sh_stack_count(s_opnd) < 2)
					continue;
				op = *(char *)sh_stack_pop(s_optr);
				n1 = (number *)sh_stack_pop(s_opnd);
				n2 = (number *)sh_stack_pop(s_opnd);
				res = sh_do_op(*n2, *n1, op);
				number_to_str(res, NULL, &len);
				number_to_str(res, buff, &len);
				if (buff[0] != 0) {
					buff[len-1] = 0;
					sh_stack_push(s_opnd, buff);
				}
				continue;
			}
		} else {
			/* skip invalid char */
				p++;
		}
	}
	retval = (number *)sh_stack_pop(s_opnd);
	if (retval)
		print_num(*retval);
err:
	/* TODO: free numbers */
	sh_stack_exit();
	if (new)
		free(new);
	return ret;
}

int shell_init(const char *conf_path)
{
	conf_fd = open(conf_path, O_RDONLY);
	if (conf_fd < 0) {
		calc_log(LOG_SHELL_ERR, "open file failure");
		return CALC_E_INIT_FAILURE;
	}

	shell_parse_conf(conf_fd);

	return CALC_E_SUCCESS;
}

void shell_exit(void)
{
	if (conf_fd > 0)
		close(conf_fd);
}
